/*
 * @Author: HJJ 03036
 * @Date: 2022-12-06 15:21:00
 * @LastEditTime: 2022-12-08 10:51:59
 * @Descripttion: （用于渲染大量复杂的结构标注，并考虑重叠问题）
 * 获取当前地图可视的标注信息数据集合，通过遍历集合来查看当前可视marker是否为聚合类，如果为非聚合类的话，当前marker数据就是原始数据可以直接标记在地图当中，如果遍历目标为聚合类，则需要利用资源对象中的getClusterLeaves方法，通过cluster_id来查找原始数据源，因为聚合之后的marker坐标，失去了原有的properties，取而代之的是聚合相关的内容属性，因此想要获取marker的name及原始经纬度，则需要二次查询
 */
import * as _ from 'lodash';
import styles from './index.less'

/**
 * 自定义Marker类
 * @example
 * ```ts
 * ```
 * @class CustomMarker
 */
export default class CustomMarker {
    /**
     * 当前地图标注总集合，通过聚合id或featureId为主键
     * @private
     * @type {Map<string,mapboxgl.Marker>}
     * @memberof CustomMarker
     */
    public readonly markers: Map<string, mapboxgl.Marker> = new Map<string, mapboxgl.Marker>();

    /**
     * 上轮地图数据变更标注集合（即本轮数据变更前，地图显示的标注集合）
     * @type {Map<string,mapboxgl.Marker>}
     * @memberof CustomMarker
     */
    public markersOnScreen: Map<string, mapboxgl.Marker> = new Map<string, mapboxgl.Marker>();

    /**
     * marker数据源标识
     * @private
     * @type {string}
     * @memberof CustomMarker
     */
    private markerSource: string;

    /**
     * Creates an instance of CustomMarker.
     * @param {mapboxgl.Map} map 地图对象
     * @param {string} sourceId 数据源标识
     * @param {number} [clusterRadius=100] marker冲突遮盖识别半径
     * @param {number} [changeZoom=8] marker转换级别
     * @memberof CustomMarker
     */
    constructor(private map: mapboxgl.Map, private sourceId: string, private clusterRadius: number = 100, private changeZoom: number = 8) {
        this.markerSource = sourceId + "_marker";
        //站点图标，后续可暴露参数
        this.map.loadImage("", (error, image) => {
            if (error) throw error;
            if (!this.map.hasImage('staion')) map.addImage('staion', image);
        })

        //低于changeZoom级别下的文本标注背景图片，后续可暴露参数
        this.map.loadImage("", (error, image) => {
            if (error) throw error;
            if (!this.map.hasImage('rectangle')) map.addImage('rectangle', image);
        })

    }

    /**
     * 添加数据
     * @param {(GeoJSON.FeatureCollection | string)} data geojson或geojson路径地址
     * @memberof CustomMarker
     */
    public addData = (data: GeoJSON.FeatureCollection | string) => {
        //添加数据源
        this.map.addSource(this.sourceId, {
            type: "geojson",
            data,
            generateId: true
        })

        //添加站点标注（maxzoom=changeZoom）
        this.map.addLayer({
            id: this.sourceId + "_text",
            type: 'symbol',
            source: this.sourceId,
            maxzoom: this.changeZoom,
            paint: {
                "text-color": "#fff",
            },
            layout: {
                'text-field': ['get', 'name'],//标注文本字段
                'text-anchor': 'bottom',
                "text-offset": [0, -1],
                'text-font': [
                    'Open Sans Semibold',
                    'Arial Unicode MS Bold'
                ],
                'icon-text-fit': 'both',
                'icon-text-fit-padding': [10, 10, 10, 10],
                'icon-image': "rectangle",//标注文本背景
                'icon-allow-overlap': true,
            }
        });

        //添加站点图标
        this.map.addLayer({
            id: this.sourceId + "_symbol",
            type: 'symbol',
            source: this.sourceId,
            layout: {
                "icon-image": "staion",//站点图标
                "icon-size": 0.6,
            },
        });

        //添加marker模拟图层（地图上不显示，只是用来获取changeZoom级别以上marker冲突避让的结果）
        this.map.addLayer({
            id: this.markerSource,
            type: 'circle',
            minzoom: 8,
            source: {
                type: "geojson",
                data,
                cluster: true,
                clusterRadius: this.clusterRadius,//marker冲突遮盖识别半径
                generateId: true
            },
            paint: {
                'circle-opacity': 0
            },
        });

        //地图监听，更新当前可视范围内的marker
        this.map.on('data', (e) => {
            if (e.sourceId !== this.markerSource || !e.isSourceLoaded) {
                return;
            }
            this.map.on('move', (e) => { this.updateMarkers(e) });
            this.map.on('moveend', (e) => { this.updateMarkers(e) });
            this.updateMarkers(e);
        })
    }

    /**
     * 更新marker(节流处理)
     * @private
     * @memberof CustomMarker
     */
    private updateMarkers = _.throttle((e) => {
        console.log("Map Event", e);
        //获取当前地图显示层级
        const zoom = this.map.getZoom();
        //小于设大的变换级别，清除marker
        if (zoom < this.changeZoom) {
            this.markersOnScreen.forEach(marker => {
                marker && marker.remove();
            })
            this.markersOnScreen.clear();
            return;
        }
        const newMarkers = new Map<string, mapboxgl.Marker>();
        const sourceObj = this.map.getSource(this.markerSource) as mapboxgl.GeoJSONSource;
        const features = this.map.querySourceFeatures(this.markerSource);
        for (let i = 0; i < features.length; i++) {
            let coords: [number, number] = (features[i].geometry as any).coordinates;
            let props = features[i].properties;
            let id = props.cluster_id;
            let marker: mapboxgl.Marker;
            if (id) {
                //聚合
                sourceObj.getClusterLeaves(id, 10, 0, (e, f) => {
                    coords = (f[0].geometry as any).coordinates;
                    props = f[0].properties;
                    marker = this.markers.get(id);
                    if (marker) {
                        //更新marker位置和html内容
                        marker.setLngLat(coords);
                        marker.getElement().innerHTML = `<div><h1>${props.name}</h1><p>${props.power}</p><p>${props.log}</p></div>`;
                    }
                })
            } else {
                //不聚合
                id = features[i].id
            }

            marker = this.markers.get(id);
            if (!marker) {
                const el = document.createElement('div');
                el.className = styles.customMaker;//通过css来调整marker样式
                el.innerHTML = `<div><h1>${props.name}</h1><p>${props.power}</p><p>${props.log}</p></div>`
                marker = new window.mapboxgl.Marker({ element: el }).setLngLat(coords).setOffset([0, -60]);//marker偏移量
                this.markers.set(id, marker);
            }

            newMarkers.set(id, marker);

            //将marker添加到地图中
            if (!this.markersOnScreen.has(id)) {
                marker && marker.addTo(this.map);
            }
        }

        //移除不在当前可视范围内的marker
        this.markersOnScreen.forEach((marker, key) => {
            if (!newMarkers.has(key)) {
                marker && marker.remove();
            }
        })
        this.markersOnScreen = newMarkers;
    }, 300)
}